home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 722 / 722.xpi / chrome / noscript.jar / content / noscript / ABE.js < prev    next >
Text File  |  2010-02-12  |  37KB  |  1,272 lines

  1. INCLUDE('IOUtil', 'antlr', 'ABEParser', 'ABELexer', 'AddressMatcher', 'Thread', 'Lang');
  2.  
  3. const ABE = {
  4.   FLAG_CALLED: 0x01,
  5.   FLAG_CHECKED: 0x02,
  6.   
  7.   SITE_RULESET_LIFETIME: 12 * 60 * 60000, // 12 hours
  8.   maxSiteRulesetSize: 8192,
  9.   maxSiteRulesetTime: 16000, // 4kbit/sec :P
  10.   enabled: false,
  11.   siteEnabled: false,
  12.   legacySupport: false,
  13.   allowRulesetRedir: false,
  14.   skipBrowserRequests: true,
  15.   
  16.   BROWSER_URI: IOS.newURI("chrome://browser/content/", null, null),
  17.   LOAD_BACKGROUND: CI.nsIChannel.LOAD_BACKGROUND,
  18.   LOAD_INITIAL_DOCUMENT_URI: CI.nsIChannel.LOAD_INITIAL_DOCUMENT_URI,
  19.   SANDBOX_KEY: "abe.sandbox",
  20.   localRulesets: [],
  21.   _localMap: null,
  22.   
  23.   _siteRulesets: null,
  24.   siteMap: {},
  25.   
  26.   get disabledRulesetNames() {
  27.     return this.rulesets.filter(function(rs) { return rs.disabled; })
  28.       .map(function(rs) { return rs.name; }).join(" ");
  29.   },
  30.   set disabledRulesetNames(names) {
  31.     var rs;
  32.     this.updateRules();
  33.     for each (rs in this.rulesets) rs.disabled = false;
  34.     if (names) try {
  35.       for each (var name in names.split(/\s+/)) {
  36.         rs = this.localMap[name] || this.siteMap[name];
  37.         if (rs) rs.disabled = true; 
  38.       }
  39.     } catch(e) {}
  40.     
  41.     return names;
  42.   },
  43.   
  44.   get localMap() {
  45.     if (this._localMap) return this._localMap;
  46.     this._localMap = {};
  47.     for each (var rs in this.localRulesets) {
  48.       this._localMap[rs.name] = rs;
  49.     }
  50.     return this._localMap;
  51.   },
  52.   
  53.   get siteRulesets() {
  54.     if (this._siteRulesets) return this._siteRulesets;
  55.     this._siteRulesets = [];
  56.     var rs;
  57.     for (var name in this.siteMap) {
  58.       rs = this.siteMap[name];
  59.       if (rs && !rs.empty) this._siteRulesets.push(rs);
  60.     }
  61.     this._siteRulesets.sort(function(r1, r2) { return r1.name > r2.name; });
  62.     return this._siteRulesets;
  63.   },
  64.   
  65.   get rulesets() {
  66.     return this.localRulesets.concat(this.siteRulesets);
  67.   },
  68.   
  69.   checkFrameOpt: function(w, chan) {
  70.     try {
  71.       if (!w) {
  72.         var ph = PolicyState.extract(chan);
  73.         var ctx = ph.context;
  74.         w = ctx.self || ctx.ownerDocument.defaultView;
  75.       }
  76.       switch (chan.getResponseHeader("X-FRAME-OPTIONS").toUpperCase()) {
  77.         case "DENY":
  78.           return true;
  79.         case "SAMEORIGIN":
  80.           return chan.URI.prePath != w.top.location.href.match(/^https?:\/\/[^\/]*/i)[0];
  81.       }
  82.     } catch(e) {}
  83.     return false;
  84.   },
  85.   
  86.   clear: function() {
  87.     this.localRulesets = [];
  88.     this.siteMap = {};
  89.     this._siteRulesets = null;
  90.     ABEStorage.reset();
  91.   },
  92.   
  93.   refresh: function() {
  94.     var disabled = this.disabledRulesetNames;
  95.     this.clear();
  96.     this.updateRulesNow();
  97.     this.disabledRulesetNames = disabled;
  98.   },
  99.   
  100.   parse: function(name, source, timestamp) {
  101.     try {
  102.       var rs =  new ABERuleset(name, source, timestamp);
  103.       if (rs.site) {
  104.         this.putSiteRuleset(rs);
  105.       } else {
  106.         this.addLocalRuleset(rs);
  107.       }
  108.       return rs;
  109.     } catch(e) {
  110.       this.log(e);
  111.     }
  112.     return false;
  113.   },
  114.   
  115.   addLocalRuleset: function(rs) {
  116.      this.localRulesets.push(rs);
  117.      this._localMap = null;
  118.   },
  119.   
  120.   putSiteRuleset: function(rs) {
  121.     this.siteMap[rs.name] = rs;
  122.     this._siteRulesets = null;
  123.   },
  124.  
  125.   serialize: function() {
  126.     var data = [];
  127.     for each (var rs in this.localRulesets) {
  128.       data.push({
  129.         source: rs.source,
  130.         name: rs.name,
  131.         timestamp: rs.timestamp,
  132.         disabled: rs.disabled
  133.       });
  134.     }
  135.     return data;
  136.   },
  137.   
  138.   restore: function(data) {
  139.     if (!data.length) return;
  140.     
  141.     var f, change;
  142.     try {
  143.       ABEStorage.clear();
  144.       ABEStorage.reset();
  145.       for each(var rs in data) {
  146.         f = ABEStorage.getRulesetFile(rs.name);
  147.         if (!f.exists()) f.create(f.NORMAL_FILE_TYPE, 0600);
  148.         IO.safeWriteFile(f, rs.source);
  149.         f.lastModifiedTime = rs.timestamp;
  150.       }
  151.       ABEStorage.loadRulesNow();
  152.     } catch(e) {
  153.       ABE.log("Failed to restore configuration: " + e);
  154.     }
  155.   },
  156.   
  157.   resetDefaults: function() {
  158.     ABEStorage.clear();
  159.     this.clear();
  160.     this.updateRulesNow();
  161.   },
  162.   
  163.   updateRules: function() {
  164.     return ABEStorage.loadRules();
  165.   },
  166.   updateRulesNow: function(reset) {
  167.     if (reset) ABEStorage.reset();
  168.     return ABEStorage.loadRulesNow();
  169.   },
  170.   getRulesetFile: function(name) {
  171.     return ABEStorage.getRulesetFile(name);
  172.   },
  173.   
  174.   checkPolicy: function(origin, destination, method) {
  175.     try {
  176.       var res = this.checkRequest(new ABERequest(new ABEPolicyChannel(origin, destination, method)));
  177.       return res && res.fatal;
  178.     } catch(e) {
  179.       ABE.log(e);
  180.       return false;
  181.     }
  182.   },
  183.   
  184.   checkRequest: function(req) {
  185.     if (!(this.enabled && (Thread.canSpin || this.legacySupport)))
  186.       return false;
  187.   
  188.     const channel = req.channel;
  189.     const loadFlags = channel.loadFlags;
  190.     
  191.     var browserReq =  req.originURI.schemeIs("chrome") && !req.external;
  192.     
  193.     if (browserReq &&
  194.         (
  195.           this.skipBrowserRequests &&
  196.           ((loadFlags & this.LOAD_BACKGROUND) ||
  197.            !req.isDoc && req.origin == ABE.BROWSER_URI.spec && !req.window)
  198.         )
  199.       ) {
  200.       if (this.consoleDump) this.log("Skipping low-level browser request for " + req.destination);
  201.       return false;
  202.     }
  203.     
  204.     this.updateRules();
  205.     
  206.     if (this.localRulesets.length == 0 && !this.siteEnabled)
  207.       return null;
  208.     
  209.     if (this.deferIfNeeded(req))
  210.       return false;
  211.     
  212.     var t;
  213.     if (this.consoleDump) {
  214.       this.log("Checking #" + req.serial + ": " + req.destination + " from " + req.origin + " - " + loadFlags);
  215.       t = Date.now();
  216.     }
  217.     
  218.     try {
  219.       var res = new ABERes(req);
  220.       var rs;
  221.       for each (rs in this.localRulesets) {
  222.         if (this._check(rs, res)) break;
  223.       }
  224.       
  225.       if (!(browserReq || res.fatal) &&
  226.           this.siteEnabled && channel instanceof CI.nsIHttpChannel &&
  227.           !IOUtil.extractFromChannel(channel, "ABE.preflight", true) &&
  228.           req.destinationURI.schemeIs("https") &&
  229.           req.destinationURI.prePath != req.originURI.prePath &&
  230.           !(this.skipBrowserRequests && req.originURI.schemeIs("chrome") && !req.window) // skip preflight for window-less browser requests
  231.       ) {
  232.         
  233.         var name = this._host2name(req.destinationURI.host);
  234.         if (!(name in this.siteMap)) {
  235.           ABE.log("Preflight for " + req.origin + ", " + req.destination + ", " + loadFlags);
  236.           this.downloadRuleset(name, req.destinationURI, req.sameQueue);
  237.         }
  238.         
  239.         rs = this.siteMap[name];
  240.         if (rs && Date.now() - rs.timestamp > this.SITE_RULESET_LIFETIME)
  241.           rs = this.downloadRuleset(name, req.destinationURI, req.sameQueue);
  242.         
  243.         if (rs) this._check(rs, res);
  244.       }
  245.     } finally {
  246.       if (this.consoleDump) this.log(req.destination + " Checked in " + (Date.now() - t));
  247.       req.checkFlags |= this.FLAG_CHECKED;
  248.     }
  249.     return res.lastRuleset && res;
  250.   },
  251.   
  252.   _check: function(rs, res) {
  253.     var action = rs.check(res.request);
  254.     if (action) {
  255.       var r = rs.lastMatch;
  256.       this.log(r);
  257.       this.log(res.request + ' matches "' + r.lastMatch + '"');
  258.       (res.rulesets || (res.rulesets = [])).push(rs);
  259.       res.lastRuleset = rs;
  260.       return res.fatal = (res.request.channel instanceof ABEPolicyChannel)
  261.         ? /^Deny$/i.test(action) 
  262.         : ABEActions[action.toLowerCase()](res.request);
  263.     }
  264.     return false;
  265.   },
  266.   
  267.   deferIfNeeded: function(req) {
  268.     var host = req.destinationURI.host;
  269.     if (!(req.canDoDNS && req.deferredDNS) ||
  270.         !ChannelReplacement.supported ||
  271.         DNS.isIP(host) ||
  272.         DNS.getCached(host) || // getCached() rather than isCached(), otherwise we defer even for lazy expiration
  273.         req.channel.redirectionLimit == 0 || req.channel.status != 0)
  274.       return false;
  275.  
  276.     IOUtil.attachToChannel(req.channel, "ABE.deferred", DUMMYOBJ);
  277.     
  278.     if (IOUtil.runWhenPending(req.channel, function() {
  279.       try {
  280.         
  281.         if (req.channel.status != 0) return;
  282.         
  283.         if ((req.channel instanceof CI.nsITransportEventSink) && req.isDoc && !req.subdoc ) try {
  284.           ABE.log("DNS notification for " + req.destination);
  285.           req.channel.onTransportStatus(null, 0x804b0003, 0, 0); // notify STATUS_RESOLVING
  286.         } catch(e) {}
  287.         
  288.         var replacement = req.replace();
  289.       
  290.         ABE.log(host + " not cached in DNS, deferring ABE checks after DNS resolution for request " + req.serial);
  291.         
  292.         
  293.         
  294.         DNS.resolve(host, 0, function(dnsRecord) {
  295.           replacement.open();
  296.         });
  297.         
  298.       } catch(e) {
  299.         ABE.log("Deferred ABE checks failed: " + e);
  300.       }
  301.     })) {
  302.       ABE.log(req.serial + " not pending yet, will check later.")
  303.     }
  304.     
  305.     return true;
  306.   },
  307.   
  308.   isDeferred: function(chan) {
  309.     return !!IOUtil.extractFromChannel(chan, "ABE.deferred", true);
  310.   },
  311.   
  312.   hasSiteRulesFor: function(host) {
  313.     return this._host2Name(host) in this.siteMap;
  314.   },
  315.   
  316.  
  317.   _host2name: function(host) {
  318.     return "." + host;
  319.   },
  320.   
  321.   isSubdomain: function(parentHost, childHost) {
  322.     if (parentHost.length > childHost.length) return false;
  323.     parentHost = "." + parentHost;
  324.     childHost = "." + childHost;
  325.     return parentHost == childHost.substring(childHost.length - parentHost.length);
  326.   },
  327.   
  328.   _downloading: {},
  329.   downloadRuleset: function(name, uri, sameQueue) {
  330.     var host = uri.host;
  331.   
  332.     var downloading = this._downloading;
  333.  
  334.     if (Thread.canSpin && (host in downloading)) {
  335.       ABE.log("Already fetching rulesets for " + host);
  336.       // Thread.yieldAll();
  337.       // Thread.spinWithQueue({ get running() { return host in downloading; }});
  338.       // return false;
  339.     }
  340.     
  341.     var ts = Date.now();
  342.     
  343.     var ctrl = {
  344.       _r: true,
  345.       set running(v) { if (!v) delete downloading[host]; return this._r = v; },
  346.       get running() { return this._r; },
  347.       startTime: ts,
  348.       maxTime: ABE.maxSiteRulesetTime
  349.     };
  350.     
  351.     var elapsed;
  352.     
  353.     try {
  354.       downloading[host] = true;
  355.       
  356.       this.log("Trying to fetch rules for " + host + ", sameQueue=" + sameQueue);
  357.       
  358.       uri = uri.clone();
  359.       uri.scheme = "https";
  360.       uri.path = "/rules.abe";
  361.         
  362.       var xhr = CC["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(CI.nsIXMLHttpRequest);
  363.       xhr.mozBackgroundRequest = true;
  364.       xhr.open("GET", uri.spec, Thread.canSpin); // async if we can spin our own event loop
  365.       
  366.       var channel = xhr.channel; // need to cast
  367.       IOUtil.attachToChannel(channel, "ABE.preflight", DUMMYOBJ);
  368.       
  369.       if (channel instanceof CI.nsIHttpChannel && !this.allowRulesetRedir)
  370.         channel.redirectionLimit = 0;
  371.       
  372.       if (channel instanceof CI.nsICachingChannel)
  373.         channel.loadFlags |= channel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY; // see bug 309424
  374.       
  375.       
  376.       xhr.onreadystatechange = function() {
  377.         switch(xhr.readyState) {
  378.           case 2:
  379.             if (xhr.status >= 400) {
  380.               ABE.log("Early abort with status " + xhr.status + " for ruleset at " + uri.spec);
  381.               break;
  382.             }
  383.             return;
  384.           case 3:
  385.             var size = xhr.responseText.length; // todo: use https://developer.mozilla.org/En/Using_XMLHttpRequest#Monitoring_progress
  386.             if (size > ABE.maxSiteRulesetSize) {
  387.               ABE.log("Ruleset at " + uri.spec + " too big: " + size + " > " + ABE.maxSiteRulesetSize);
  388.               break;
  389.             }
  390.             return;
  391.           case 4:
  392.             // end
  393.             ctrl.running = false;
  394.             return;
  395.           default: // 0, 1
  396.             return;
  397.         }
  398.         xhr.abort();
  399.         ctrl.running = false;
  400.       }
  401.       
  402.       if (Thread.canSpin) {
  403.         var send = function() {
  404.           xhr.send(null);
  405.           return Thread.spin(ctrl);
  406.         };
  407.         
  408.         if (sameQueue ? send() : Thread.runWithQueue(send)) {
  409.           var size = 0;
  410.           try {
  411.             size = xhr.responseText.length;
  412.           } catch(e) {}
  413.           ABE.log("Ruleset at " + uri.spec + " timeout: " + size + " chars received in " + ctrl.elapsed + "ms");
  414.           xhr.abort();
  415.           return false;
  416.         }
  417.       } else {
  418.         xhr.send(null);
  419.       }
  420.       
  421.       if (xhr.channel != channel && xhr.channel) // shouldn't happen, see updateRedirectChain()...
  422.         this._handleDownloadRedirection(channel, xhr.channel); 
  423.  
  424.       if (xhr.status != 200)
  425.         throw new Error("Status: " + xhr.status);
  426.       
  427.       if (!/^application\/|\babe\b|^text\/plain$/i.test(xhr.channel.contentType))
  428.         throw new Error("Content-type: " + xhr.channel.contentType);
  429.       
  430.       var source = xhr.responseText || '';
  431.       
  432.       elapsed = Date.now() - ts;
  433.       if (source) ABE.log("Fetched ruleset for "+ host + " in " + elapsed + "ms");
  434.       
  435.       return this.parse(name, source);
  436.     } catch(e) {
  437.       elapsed = elapsed || Date.now() - ts;
  438.       this.log("Can't fetch " + uri.spec + " (" + elapsed + "ms elapsed)");
  439.       this.log(e.message);
  440.     } finally {
  441.       if (!(name in this.siteMap)) this.parse(name, '');
  442.       else this.siteMap[name].timestamp = ts;
  443.       ctrl.running = false;
  444.     }
  445.     
  446.     return false;
  447.   },
  448.   
  449.   
  450.   isSandboxed: function(channel) {
  451.     return IOUtil.extractFromChannel(channel, ABE.SANDBOX_KEY, true);
  452.   },
  453.   setSandboxed: function(channel) {
  454.     IOUtil.attachToChannel(channel, ABE.SANDBOX_KEY, DUMMYOBJ);
  455.   },
  456.   sandbox: function(docShell) {
  457.     docShell.allowJavascript = docShell.allowPlugins =
  458.         docShell.allowMetaRedirects= docShell.allowSubframes = false;
  459.   },
  460.   
  461.   
  462.   updateRedirectChain: function(oldChannel, newChannel) {
  463.     this._handleDownloadRedirection(oldChannel, newChannel);
  464.     
  465.     var redirectChain = this.getRedirectChain(oldChannel);
  466.     redirectChain.push(oldChannel.URI);
  467.     IOUtil.attachToChannel(newChannel, "ABE.redirectChain", redirectChain);
  468.   },
  469.   
  470.   getRedirectChain: function(channel) {
  471.     var rc = IOUtil.extractFromChannel(channel, "ABE.redirectChain", true);
  472.     if (!rc) {
  473.       var origin = ABERequest.getOrigin(channel);
  474.       rc = origin ? [origin] : [];
  475.     };
  476.     return rc;
  477.   },
  478.   
  479.   getOriginalOrigin: function(channel) {
  480.     var rc = this.getRedirectChain(channel);
  481.     return rc.length && rc[0] || null;
  482.   },
  483.   
  484.   _handleDownloadRedirection: function(oldChannel, newChannel) {
  485.     if (!IOUtil.extractFromChannel(oldChannel, "ABE.preflight", true)) return;
  486.     
  487.     var uri = oldChannel.URI;
  488.     var newURI = newChannel.URI;
  489.         
  490.     if (uri.spec != newURI.spec && // redirected, check if it same path and same domain or upper
  491.         (uri.path != newURI.path || 
  492.           !(newURI.schemeIs("https") && this.isSubdomain(newURI.host, uri.host))
  493.         )
  494.       ) {
  495.       var msg = "Illegal ABE rule redirection " + uri.spec + " -> " + newURI.spec;
  496.       ABE.log(msg);
  497.       oldChannel.cancel(NS_BINDING_ABORTED);
  498.       throw new Error(msg);
  499.     }
  500.     
  501.     IOUtil.attachToChannel(oldChannel, "ABE.preflight", DUMMYOBJ);
  502.   },
  503.   
  504.   
  505.   consoleDump: false,
  506.   log: function(msg) {
  507.     if (this.consoleDump) {
  508.       if (msg.stack) msg = msg.message + "\n" + msg.stack;
  509.       dump("[ABE] " + msg + "\n");
  510.     }
  511.   }
  512. }
  513.  
  514. function ABERes(req) {
  515.   this.request = req;
  516. }
  517.  
  518. ABERes.prototype = {
  519.   rulesets: null,
  520.   lastRuleset: null,
  521.   fatal: false
  522. }
  523.  
  524. var ABEActions = {
  525.   accept: function(req) {
  526.     return false;  
  527.   },
  528.   deny: function(req) {
  529.     IOUtil.abort(req.channel, true);
  530.     return true;
  531.   },
  532.   anonymize: function(req, channel) {
  533.     channel = channel || req.channel;
  534.     if (channel.loadFlags & channel.LOAD_ANONYMOUS) // already anonymous
  535.       return false;
  536.     
  537.     var uri = req.destinationURI;
  538.     var cookie;
  539.     try {
  540.       cookie = channel.getRequestHeader("Cookie");
  541.     } catch(e) {
  542.       cookie = '';
  543.     }
  544.     uri = IOUtil.anonymizeURI(uri.clone(), cookie);
  545.     
  546.     if (channel.isPending()) { // channel is already opened, we must replace it
  547.       
  548.       if (ChannelReplacement.supported) {
  549.         try {
  550.           var replacement = req.replace(
  551.               /^(?:GET|HEAD|OPTIONS)$/i.test(channel.requestMethod) ? null : "GET",
  552.               uri);
  553.           
  554.           this.anonymize(req, replacement.channel);
  555.           replacement.open();
  556.           return false;
  557.         } catch(e) {
  558.           ABE.log(e);
  559.         }
  560.       }
  561.       ABE.log("Counldn't replace " + uri.spec + " for Anonymize, falling back to Deny.");
  562.       return this.deny(req);
  563.     }
  564.     
  565.     if (uri.spec != channel.URI.spec) channel.URI.spec = uri.spec;
  566.     channel.setRequestHeader("Cookie", '', false);
  567.     channel.setRequestHeader("Authorization", '', false);
  568.     channel.loadFlags |= channel.LOAD_ANONYMOUS;
  569.     return false;
  570.   },
  571.   
  572.   sandbox: function(req) {
  573.     ABE.setSandboxed(req.channel);
  574.     if (req.isDoc) {
  575.       var docShell = DOM.getDocShellForWindow(req.window);
  576.       if (docShell) ABE.sandbox(docShell);
  577.     }
  578.     return false;
  579.   }
  580. }
  581.  
  582.  
  583. function ABERuleset(name, source, timestamp) {
  584.   this.name = name;
  585.   this.site = /\./.test(name);
  586.   this.source = source;
  587.   this.empty = !source;
  588.   this.timestamp = timestamp || Date.now();
  589.   if (!this.empty) {
  590.     try {
  591.       // dirty hack
  592.       var self = this;
  593.       org.antlr.runtime.BaseRecognizer.prototype.emitErrorMessage = function(msg) {
  594.         // we abort immediately to prevent infinite loops
  595.         var m = msg.match(/^line (\d+)/i, msg);
  596.         if (m) throw new Error(msg, parseInt(m[1]), ABE.getRulesetFile(self.name)); // TODO: error console reporting w/ line num
  597.         throw new Error(msg)
  598.       };
  599.       
  600.       this._init(new ABEParser(new org.antlr.runtime.CommonTokenStream(
  601.         new ABELexer(new org.antlr.runtime.ANTLRStringStream(source))))
  602.             .ruleset().getTree());
  603.     } catch(e) {
  604.       if (this.errors) this.errors.push(e.message)
  605.       else this.errors = [e.message];
  606.     }
  607.   }
  608. }
  609.  
  610. ABERuleset.prototype = {
  611.   site: false,
  612.   empty: false,
  613.   errors: null,
  614.   disabled: false,
  615.   rules: [],
  616.   expires: 0,
  617.   
  618.   _init: function(tree) {
  619.     var rule = null,
  620.         predicate = null,
  621.         accumulator = null,
  622.         history  = [],
  623.         rules = [];
  624.     
  625.     walk(tree);
  626.     
  627.     if (!this.errors) this.rules = rules;
  628.     rule = predicate = accumulator = history = null;
  629.   
  630.     
  631.     function walk(tree) {
  632.       var node, t;
  633.       for (var j = 0, l = tree.getChildCount(); j < l; j++) {
  634.         node = tree.getChild(j);
  635.         examine(node);
  636.         walk(node.getTree());
  637.       }
  638.     }
  639.     
  640.     function examine(node) {
  641.       var t = node.getToken();
  642.       
  643.       switch(t.type) {
  644.         case ABEParser.T_SITE:
  645.         case ABEParser.EOF:
  646.           if (rule) commit();
  647.           if (t.type == ABEParser.T_SITE) {
  648.             rule = { destinations: [], predicates: [] };
  649.             accumulator = rule.destinations;        
  650.           }
  651.           break;
  652.         case ABEParser.T_ACTION:
  653.           if (rule) {
  654.             rule.predicates.push(predicate = { actions: [], methods: [], origins: [] });
  655.             accumulator = predicate.actions;
  656.           }
  657.           break;
  658.         case ABEParser.T_METHODS:
  659.           accumulator = predicate.methods;
  660.           break;
  661.         case ABEParser.T_FROM:
  662.           accumulator = predicate.origins;
  663.           break;
  664.         case ABEParser.COMMENT:
  665.           break;
  666.         default:
  667.           if (accumulator) accumulator.push(node.getText());
  668.       }
  669.     }
  670.     
  671.     function commit() {
  672.       rules.push(new ABERule(rule.destinations, rule.predicates));
  673.       rule = null;
  674.     }
  675.   },
  676.   
  677.   lastMatch: null,
  678.     check: function(req) {
  679.     if (this.disabled) return '';
  680.     
  681.         var res;
  682.         for each (var r in this.rules) {
  683.             res = r.check(req);
  684.             if (res) {
  685.         this.lastMatch = r;
  686.         return res;
  687.       }
  688.         }
  689.         return '';
  690.     }
  691. }
  692.  
  693. function ABERule(destinations, predicates) {
  694.   this.destinations = destinations.join(" ");
  695.   this.destination = new AddressMatcher(destinations.filter(this._destinationFilter, this).join(" "));
  696.   this.predicates = predicates.map(ABEPredicate.create);
  697. }
  698.  
  699. ABERule.prototype = {
  700.   local: false,
  701.   
  702.     allDestinations: false,
  703.   lastMatch: null,
  704.     _destinationFilter: function(s) {
  705.         switch(s) {
  706.             case "SELF":
  707.                 return false; // this is illegal, should we throw an exception?
  708.             case "LOCAL":
  709.                 return !(this.local = true);
  710.             case "ALL":
  711.                 return !(this.allDestinations = true);
  712.         }
  713.         return true;
  714.     },
  715.     
  716.   check: function(req) {
  717.     if (!req.failed &&
  718.         (this.allDestinations ||
  719.           this.destination && this.destination.test(req.destination, req.canDoDNS, false) ||
  720.           this.local && req.localDestination)
  721.         ) {
  722.       for each (var p in this.predicates) {
  723.         if (p.match(req)) {
  724.           this.lastMatch = p;
  725.           return p.action;
  726.         }
  727.         if (req.failed) break;
  728.       }
  729.     }
  730.     return '';
  731.   },
  732.   
  733.   toString: function() {
  734.     var s = "Site " + this.destinations + "\n" + this.predicates.join("\n");
  735.     this.toString = function() { return s; };
  736.     return s;
  737.   }
  738. }
  739.  
  740. function ABEPredicate(p) {
  741.   this.action = p.actions[0];
  742.  
  743.   if (this.action == 'Accept') {
  744.     this.permissive = true;
  745.   } else if (/^(Logout|Anon)$/.test(this.action)) {
  746.     this.action = 'Anonymize';
  747.   }
  748.   
  749.   this.methods = p.methods.join(" ");
  750.     if (this.methods.length) {
  751.       this.allMethods = false;
  752.       var mm = p.methods.filter(this._methodFilter, this);
  753.       if (mm.length) this.methodRx = new RegExp("^\\b(?:" + mm.join("|") + ")\\b$", "i");
  754.     }
  755.     this.origins = p.origins.join(" ");
  756.     if (p.origins.length) {
  757.             this.allOrigins = false;
  758.     if (this.permissive) { // if Accept any, accept browser URLs 
  759.       p.origins.push("^(?:chrome|resource):");
  760.     }
  761.     this.origin = new AddressMatcher(p.origins.filter(this._originFilter, this).join(" "));
  762.   }
  763. }
  764. ABEPredicate.create = function(p) { return new ABEPredicate(p); };
  765. ABEPredicate.prototype = {
  766.   permissive: false,
  767.   
  768.   subdoc: false,
  769.     self: false,
  770.     local: false,
  771.     
  772.     allMethods: true,
  773.     allOrigins: true,
  774.     
  775.     methodRx: null,
  776.     origin: null,
  777.   
  778.     _methodFilter: function(m) {
  779.         switch(m) {
  780.             case "SUB":
  781.                 return !(this.subdoc = true);
  782.             case "ALL":
  783.                 return !(this.allMethods = true);
  784.         }
  785.         return true;
  786.     },
  787.     _originFilter: function(s) {
  788.         switch(s) {
  789.             case "SELF":
  790.                 return !(this.self = true);
  791.             case "LOCAL":
  792.                 return !(this.local = true);
  793.             case "ALL":
  794.                 return !(this.allOrigins = true);
  795.         }
  796.         return true;
  797.     },
  798.     
  799.   match: function(req) {
  800.     return (this.allMethods || this.subdoc && req.isSubdoc ||
  801.                         this.methodRx && this.methodRx.test(req.method)) &&
  802.             (this.allOrigins || this.self && req.isSelf ||
  803.                 (this.permissive ? req.matchAllOrigins(this.origin) : req.matchSomeOrigins(this.origin)) ||
  804.                 this.local && req.localOrigin
  805.             );
  806.   },
  807.   
  808.   toString: function() {
  809.     var s = this.action;
  810.     if (this.methods) s += " " + this.methods;
  811.     if (this.origins) s += " from " + this.origins;
  812.     this.toString = function() { return s; };
  813.     return s;
  814.   }
  815. }
  816.  
  817. function ABEPolicyChannel(origin, destination, method) {
  818.   this.originURI = origin;
  819.   this.URI = destination;
  820.   if (method) this.requestMethod = method;
  821. }
  822. ABEPolicyChannel.prototype = {
  823.   requestMethod: "GET",
  824.   cancelled: false,
  825.   loadFlags: 0,
  826.   cancel: function() {
  827.     this.cancelled = true;
  828.   }
  829. }
  830.  
  831. function ABERequest(channel) {
  832.     this._init(channel);
  833. }
  834.  
  835. ABERequest.serial = 0;
  836.  
  837. ABERequest.getOrigin = function(channel) {
  838.   return IOUtil.extractFromChannel(channel, "ABE.origin", true);
  839. },
  840. ABERequest.getLoadingChannel = function(window) {
  841.   return window && ("__loadingChannel__" in window) && window.__loadingChannel__;
  842. },
  843.  
  844. ABERequest.storeOrigin = function(channel, originURI) {
  845.   IOUtil.attachToChannel(channel, "ABE.origin", originURI);
  846. },
  847.  
  848. ABERequest.clear = function(channel, window) {
  849.   IOUtil.extractFromChannel(channel, "ABE.origin");
  850. }
  851.  
  852. ABERequest.count = 0;
  853.  
  854.  
  855. ABERequest.prototype = Lang.memoize({
  856.     external: false,
  857.     failed: false,
  858.   checkFlags: 0,
  859.   deferredDNS: true,
  860.   replaced: false,
  861.   
  862.   _init: function(channel) {
  863.     this.serial = ABERequest.serial++;
  864.     this.channel = channel;
  865.     this.method = channel.requestMethod;
  866.     this.destinationURI = IOUtil.unwrapURL(channel.URI);
  867.     this.destination = this.destinationURI.spec;
  868.     this.early = channel instanceof ABEPolicyChannel;
  869.     this.isDoc = !!(channel.loadFlags & channel.LOAD_DOCUMENT_URI);
  870.     
  871.     if (this.isDoc) {
  872.       var w = this.window;
  873.       if (w) w.__loadingChannel__ = channel;
  874.     }
  875.     
  876.     var ou = ABERequest.getOrigin(channel);
  877.     if (ou) {
  878.       this.xOriginURI = this.originURI = ou;
  879.       this.xOrigin = this.origin = ou.spec;
  880.       this.replaced = true;
  881.     } else {
  882.       this.xOriginURI = this.early
  883.         ? channel.originURI
  884.         : XOriginCache.pick(this.destinationURI, true) || // picks and remove cached entry
  885.             ((channel.originalURI.spec != this.destination) 
  886.               ? channel.originalURI 
  887.               : IOUtil.extractInternalReferrer(channel)
  888.             ) || null;
  889.       
  890.       this.xOrigin = this.xOriginURI && this.xOriginURI.spec || '';
  891.       
  892.       var ou = this.xOrigin && this.xOriginURI;
  893.       if (!ou) {
  894.         if (channel instanceof CI.nsIHttpChannelInternal) {
  895.           ou = channel.documentURI;
  896.           if (!ou || ou.spec == this.destination) ou = null;
  897.         }
  898.       }
  899.       if (this.isDoc && (!ou || /^(?:javascript|data)$/i.test(ou.scheme))) {
  900.         ou = this.traceBack;
  901.         if (ou) ou = IOS.newURI(ou, null, null);
  902.       }
  903.       
  904.       this.originURI = ou && IOUtil.unwrapURL(ou) || ABE.BROWSER_URI;
  905.       
  906.       this.origin = this.originURI && this.originURI.spec || '';
  907.     
  908.       ABERequest.storeOrigin(channel, this.originURI);
  909.     }
  910.   },
  911.   
  912.   
  913.   
  914.   
  915.   replace: function(newMethod, newURI) {
  916.     var replacement = new ChannelReplacement(this.channel, newURI, newMethod)
  917.       .replace(newMethod || newURI);
  918.     
  919.     return replacement;
  920.   },
  921.   
  922.   isBrowserURI: function(uri) {
  923.     return /^(?:chrome|resource)$/i.test(uri.scheme);
  924.   },
  925.   
  926.   isLocal: function(uri, all) {
  927.     return DNS.isLocalURI(uri, all);
  928.   },
  929.   
  930.   _checkLocalOrigin: function(uri) {
  931.     try {
  932.       return !this.failed && uri && (this.isBrowserURI(uri) || this.isLocal(uri, true)); // we cache external origins to mitigate DNS rebinding
  933.     } catch(e) {
  934.       ABE.log("Local origin DNS check failed for " + uri.spec + ": " + e);
  935.       try {
  936.         if (this.destinationURI.host == uri.host) {
  937.           this.channel.cancel(NS_ERROR_UNKNOWN_HOST);
  938.           this.failed = true;
  939.         }
  940.       } catch(e) {
  941.       }
  942.       return false;
  943.     }
  944.   },
  945.   
  946.   _checkSelf: function(originURI) {
  947.     return originURI &&  (this.isBrowserURI(originURI) || originURI.prePath == this.destinationURI.prePath);
  948.   },
  949.   
  950.   matchAllOrigins: function(matcher) {
  951.     var canDoDNS = this.canDoDNS;
  952.     return (canDoDNS && matcher.netMatching) 
  953.       ? matcher.testURI(this.originURI, canDoDNS, true) &&
  954.           this.redirectChain.every(function(uri) { return matcher.testURI(uri, canDoDNS, true); })
  955.       : matcher.test(this.origin) && this.redirectChain.every(matcher.testURI, matcher)
  956.       ;
  957.   },
  958.   
  959.   matchSomeOrigins: function(matcher) {
  960.     var canDoDNS = this.canDoDNS;
  961.     return (canDoDNS && matcher.netMatching) 
  962.       ? matcher.testURI(this.originURI, canDoDNS, false) ||
  963.           this.redirectChain.some(function(uri) { return matcher.testURI(uri, canDoDNS, false); })
  964.       : matcher.test(this.origin) || this.redirectChain.some(matcher.testURI, matcher)
  965.       ;
  966.   },
  967.   
  968.   toString: function() {
  969.     var s = "{" + this.method + " " + this.destination + " <<< " +
  970.       this.redirectChain.reverse().map(function(uri) { return uri.spec; }).concat(this.origin)
  971.         .join(", ") + "}";
  972.     this.toString = function() { return s; }
  973.     return s;
  974.   }
  975. },
  976. // lazy properties
  977. {
  978.   traceBack: function() {
  979.     this.breadCrumbs = [this.destination];
  980.     return !this.early && OriginTracer.traceBack(this, this.breadCrumbs);
  981.   },
  982.   traceBackURI: function() {
  983.     var tbu = this.traceBack;
  984.     return tbu && IOS.newURI(tbu, null, null);
  985.   },
  986.   canDoDNS: function() {
  987.     return (this.channel instanceof CI.nsIChannel) && // we want to prevent sync DNS resolution for resources we didn't already looked up
  988.       IOUtil.canDoDNS(this.channel);
  989.   },
  990.   localOrigin: function() {
  991.     return this.canDoDNS &&  this._checkLocalOrigin(this.originURI) &&
  992.         this.redirectChain.every(this._checkLocalOrigin, this);
  993.   },
  994.   localDestination: function() {
  995.     try {
  996.       return !this.failed && this.canDoDNS && this.isLocal(this.destinationURI, false);
  997.     } catch(e) {
  998.       ABE.log("Local destination DNS check failed for " + this.destination +" from "+ this.origin + ": " + e);
  999.       this.channel.cancel(NS_ERROR_UNKNOWN_HOST);
  1000.       this.failed = true;
  1001.       return false;
  1002.     }
  1003.   },
  1004.   isSelf: function() {
  1005.     return this._checkSelf(this.originURI) && this.redirectChain.every(this._checkSelf, this);
  1006.   },
  1007.   isSubdoc: function() {
  1008.     var channel = this.channel;
  1009.     if (this.isDoc) {
  1010.       var w = this.window;
  1011.       return w != w.top;
  1012.     }
  1013.     return false;
  1014.   },
  1015.   redirectChain: function() {
  1016.     return ABE.getRedirectChain(this.channel);
  1017.   },
  1018.   sameQueue: function() {
  1019.     return this.isDoc || !!(this.channel.loadFlags & this.channel.LOAD_BACKGROUND) || !this.window
  1020.       ;
  1021.   },
  1022.   window: function() {
  1023.     return IOUtil.findWindow(this.channel);
  1024.   }
  1025. }
  1026. ); // end memoize
  1027.  
  1028.  
  1029. var ABEStorage = {
  1030.   _lastCheckTS: 0,
  1031.   _delay: 360000, // wait 1 hour to check and fetch
  1032.   _defaults: {
  1033.     SYSTEM: "# Prevent Internet sites from requesting LAN resources.\r\nSite LOCAL\r\nAccept from LOCAL\r\nDeny",
  1034.     USER: "# User-defined rules. Feel free to experiment here.\r\n\r\n"
  1035.   },
  1036.   
  1037.   _initDefaults: function(dir) {
  1038.     try {
  1039.       var f, content;
  1040.       for (var d in this._defaults) {
  1041.         f = dir.clone();
  1042.         f.append(d + ".abe");
  1043.         if (!f.exists()) {
  1044.           f.create(f.NORMAL_FILE_TYPE, 0600);
  1045.           IO.safeWriteFile(f, this._defaults[d]);
  1046.         }
  1047.       }
  1048.     } catch(e) {
  1049.       ABE.log(e);
  1050.     }
  1051.   },
  1052.   
  1053.   _initDir: function(dir) {
  1054.     if (!dir.exists()) try {
  1055.       dir.create(dir.DIRECTORY_TYPE, 0755);
  1056.       this._initDefaults(dir);
  1057.     } catch(e) {
  1058.       ABE.log(e);
  1059.     }
  1060.   },
  1061.   
  1062.   get dir() {
  1063.     var dir = CC["@mozilla.org/file/directory_service;1"].getService(
  1064.         CI.nsIProperties).get("ProfD", CI.nsIFile);
  1065.     dir.append("ABE");
  1066.     dir.append("rules");
  1067.     this._initDir(dir);
  1068.     
  1069.     delete this.dir;
  1070.     return this.dir = dir;
  1071.   },
  1072.   
  1073.   
  1074.   
  1075.   clear: function() {
  1076.     this.dir.remove(true);
  1077.     this._initDir(this.dir);
  1078.   },
  1079.   
  1080.   reset: function() {
  1081.     this._lastCheckTS = 0;
  1082.   },
  1083.   
  1084.   getRulesetFile: function(name) {
  1085.     var f = this.dir.clone();
  1086.     f.append(name + ".abe");
  1087.     return f;
  1088.   },
  1089.   
  1090.   loadRules: function() {
  1091.     return !(this._lastCheckTS &&
  1092.               Date.now() - this._lastCheckTS < this._delay) &&
  1093.           this.loadRulesNow();
  1094.   },
  1095.   
  1096.   loadRulesNow: function() {
  1097.     ABE.log("Checking for updated rules...");
  1098.     var t = Date.now();
  1099.     try {
  1100.       var dir = this.dir;
  1101.       try {
  1102.         var entries = dir.directoryEntries;
  1103.       } catch(e) {
  1104.         this._initDir(dir);
  1105.         entries =  dir.directoryEntries;
  1106.       }
  1107.       var ff = [];
  1108.       var mustUpdate = dir.lastModifiedTime > this._lastCheckTS;
  1109.       var f;
  1110.       while(entries.hasMoreElements()) {
  1111.         f = entries.getNext();
  1112.         if (f instanceof CI.nsIFile && /^[^\.\s]*\.abe$/i.test(f.leafName)) {
  1113.           ff.push(f);
  1114.           if (!mustUpdate && f.lastModifiedTime > this._lastCheckTS) mustUpdate = true;
  1115.         }
  1116.       }
  1117.       
  1118.       if (!mustUpdate) return false;
  1119.       
  1120.       ABE.log("Rules changed, reloading!")
  1121.       
  1122.       ff.sort(function(a, b) { return a.leafName > b.leafName; });
  1123.       
  1124.       var disabledNames = ABE.disabledRulesetNames;
  1125.       ABE.clear();
  1126.       ff.forEach(this.loadRuleFile, this);
  1127.     } catch(e) {
  1128.       ABE.log(e);
  1129.       return false;
  1130.     } finally {
  1131.       this._lastCheckTS = Date.now();
  1132.       ABE.log("Updates checked in " + (this._lastCheckTS - t) + "ms");
  1133.     }
  1134.     ABE.disabledRulesetNames = disabledNames;
  1135.     return true;
  1136.   },
  1137.   
  1138.   loadRuleFile: function(f) {
  1139.     try {
  1140.       ABE.parse(f.leafName.replace(/\.abe$/i, ''), IO.readFile(f), f.lastModifiedTime);
  1141.     } catch(e) {
  1142.       ABE.log(e);
  1143.     }
  1144.   }
  1145.   
  1146. }
  1147.  
  1148.  
  1149. var XOriginCache = {
  1150.   LIFE: 180000, // 3 mins, more than enough for most DNS timeout confs
  1151.   PURGE_INTERVAL: 60000,
  1152.   MAX_ENTRIES: 100,
  1153.   _entries: [],
  1154.   _lastPurge: Date.now(),
  1155.   _lastDestination: null,
  1156.   
  1157.   store: function(origin, destination) {
  1158.     if (destination === this._lastDestination)
  1159.       return; // we can afford it because we deal with nsIURI pointers and origins are invariants
  1160.     
  1161.     this._lastDestination = destination;
  1162.     
  1163.     var ts = Date.now();
  1164.     this._entries.push({ o: origin, d: destination, ts: ts });
  1165.     
  1166.     if (this._entries.length > this.MAX_ENTRIES)
  1167.       this._entries.shift();
  1168.       
  1169.     if (ts - this._lastPurge > this.PURGE_INTERVAL) this.purge(ts);
  1170.   },
  1171.   pick: function(destination, remove) {
  1172.     var ee = this._entries;
  1173.     for (var j = ee.length, e;  j--> 0;) {
  1174.       if ((e = ee[j]).d === destination) {
  1175.         if (remove) ee.splice(j, 1);
  1176.         return e.o;
  1177.       }
  1178.     }
  1179.     return null;
  1180.   },
  1181.   purge: function(ts) {
  1182.     ts = ts || Date.now();
  1183.     var ee = this._entries;
  1184.     var j = 0, len = ee.length;
  1185.     for(; j < len && ee[j].ts + this.LIFE < ts; j++);
  1186.     if (j > 0) ee.splice(0, j);
  1187.     this._lastPurge = ts;
  1188.     this._lastDestination = null;
  1189.   }
  1190. }
  1191.  
  1192. var OriginTracer = {
  1193.   detectBackFrame: function(prev, next, ds) {
  1194.     if (prev.ID != next.ID) return prev.URI.spec;
  1195.     if ((prev instanceof CI.nsISHContainer) &&
  1196.        (next instanceof CI.nsISHContainer) &&
  1197.        (ds instanceof CI.nsIDocShellTreeNode)
  1198.       ) {
  1199.       var uri;
  1200.       for (var j = Math.min(prev.childCount, next.childCount, ds.childCount); j-- > 0;) {
  1201.         uri = this.detectBackFrame(prev.GetChildAt(j),
  1202.                                    next.GetChildAt(j),
  1203.                                    ds.GetChildAt(j));
  1204.         if (uri) return uri.spec;
  1205.       }
  1206.     }
  1207.     return null;
  1208.   },
  1209.   
  1210.   traceBackHistory: function(sh, window, breadCrumbs) {
  1211.     var wantsBreadCrumbs = !breadCrumbs;
  1212.     breadCrumbs = breadCrumbs || [window.document.documentURI];
  1213.     
  1214.     var he;
  1215.     var uri = null;
  1216.     var site = '';
  1217.     for (var j = sh.index; j > -1; j--) {
  1218.        he = sh.getEntryAtIndex(j, false);
  1219.        if (he.isSubFrame && j > 0) {
  1220.          uri = this.detectBackFrame(sh.getEntryAtIndex(j - 1), h,
  1221.            DOM.getDocShellForWindow(window)
  1222.          );  
  1223.        } else {
  1224.         // not a subframe navigation 
  1225.         if (window == window.top) {
  1226.           uri = he.URI.spec; // top frame, return history entry
  1227.         } else {
  1228.           window = window.parent;
  1229.           uri = window.document.documentURI;
  1230.         }
  1231.       }
  1232.       if (!uri) break;
  1233.       if (breadCrumbs[0] && breadCrumbs[0] == uri) continue;
  1234.       breadCrumbs.unshift(uri);
  1235.       if (!/^(?:javascript|data)$/i.test(uri.scheme)) {
  1236.         site = uri;
  1237.         break;
  1238.       }
  1239.     }
  1240.     return wantsBreadCrumbs ? breadCrumbs : site;
  1241.   },
  1242.   
  1243.   traceBack: function(req, breadCrumbs) {
  1244.     var res = '';
  1245.         try {
  1246.       ABE.log("Traceback origin for " + req.destination);
  1247.       var window = req.window;
  1248.       if (window instanceof CI.nsIInterfaceRequestor) {
  1249.         var webNav = window.getInterface(CI.nsIWebNavigation);
  1250.         var current = webNav.currentURI;
  1251.         var isSameURI = current && current.equals(req.destinationURI);
  1252.         if (isSameURI && (req.channel.loadFlags & req.channel.VALIDATE_ALWAYS)) 
  1253.           return req.destination; // RELOAD
  1254.  
  1255.         const sh = webNav.sessionHistory;
  1256.         res = sh ? this.traceBackHistory(sh, window, breadCrumbs || null) 
  1257.                   : (!isSameURI && current) 
  1258.                     ? req.destination
  1259.                     : '';
  1260.        if (res == "about:blank") {
  1261.          res = window.parent.location.href;
  1262.          ns.dump(res);
  1263.        }
  1264.       }
  1265.     } catch(e) {
  1266.       ABE.log("Error tracing back origin for " + req.destination + ": " + e.message);
  1267.     }
  1268.     ABE.log("Traced back " + req.destination + " to " + res);
  1269.     return res;
  1270.   }
  1271. }
  1272.